/*******************************************************************************
 * Copyright (c) 2007, Geophysical Center RAS
 * Sergey Berezin, Dmitry Voitsekhovskiy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * - Neither the name of the Geophysical Center RAS nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using VisualEsse.Visualization.VertexTypes;
using SvgNet.SvgGdi;
using System.Drawing.Drawing2D;
using System.Diagnostics;

namespace VisualEsse.Visualization
{
    /// <summary>
    /// Defines a drawing methods for GeometricObjects that are based upon GDI+.
    /// </summary>
    public interface IGDIGeometricObject
    {
        void Draw();

        void Draw(Matrix transformation);
    }

    public class GDITextureArray : TextureArray
    {
        private IGraphics graphics;

        internal GDITextureArray(IGraphics graphics) : base()
        {
            this.graphics = graphics;
        }
        
        public override void Dispose()
        {
            base.Dispose();
        } 

    }

    /// <summary>
    /// Stores vertices and is designated for the Direct3D usage.
    /// </summary>
    /// <typeparam name="Vertex"></typeparam>
    public class GDIVertexArray<Vertex> : VertexArray<Vertex>
    {
        /// <summary>
        /// Creates a new instance of the generic GDIVertexArray class,
        /// where Vertex is a type of used vertices.
        /// </summary>
        internal GDIVertexArray(IGraphics graphics)
        {
        }

        public List<Vertex> VertexBuffer
        {
            get
            {
                return vertices;
            }
        }
        

        public override void ProcessVertices(VertexArray<Vertex>.ProcessVerticesProcedure processor)
        {
            base.ProcessVertices(processor);
        }

       

        public override void Dispose()
        {
            base.Dispose();
        }
    }

    /// <summary>
    /// Represents a Direct3D primitive.
    /// </summary>
    public class GDIPrimitives : Primitives
    {
        private IGraphics graphics;

        /// <summary>
        /// Creates a new instance of the GDIPrimitive class.
        /// </summary>
        /// <param name="type">Type of the primitive.</param>
        internal GDIPrimitives(IGraphics graphics, PrimitiveType type)
            : base(type)
        {
            this.graphics = graphics;
        }        

        public override void Dispose()
        {
            base.Dispose();
        }
    }

    public class GDIPrimitiveArray : PrimitiveArray
    {
        private IGraphics graphics;

        internal GDIPrimitiveArray(IGraphics graphics)
        {
            this.graphics = graphics;
        }

        public override Primitives CreatePrimitives(PrimitiveType type)
        {
            return new GDIPrimitives(graphics, type);
        }
    }    

    public class GDIPrimitiveGeometricObject<Vertex> : PrimitiveGeometricObject<Vertex>, IGDIGeometricObject
    {
        private IGraphics graphics;

        internal GDIPrimitiveGeometricObject(IGraphics graphics)
        {
            this.graphics = graphics;
            vertices = new GDIVertexArray<Vertex>(graphics);
            primitives = new GDIPrimitiveArray(graphics);
            textures = new GDITextureArray(graphics);
        }

        public void Draw()
        {
            GDIVertexArray<Vertex> v = (GDIVertexArray<Vertex>)vertices;

            for (int i = 0; i < primitives.Count; i++)
            {
                GDIPrimitives p = (GDIPrimitives)primitives[i];

                switch (p.Type)
                {
                    //case PrimitiveType.PointList:
                    //    break;
                    //case PrimitiveType.LineList:
                    //    break;
                    //case PrimitiveType.TriangleList:
                    //    break;
                    //case PrimitiveType.TriangleStrip:
                    //    break;
                    //case PrimitiveType.TriangleFan:
                    //    break;
                    case PrimitiveType.LineStrip:
                        DrawLineStrip(p, v);
                        break;
                    default:
                        throw new ApplicationException("GDI GeometryObject: Primitive with type " + p.Type
                            + " is not supported.");
                }                
            }
        }

        public void Draw(Matrix transformation)
        {
            GDIVertexArray<Vertex> v = (GDIVertexArray<Vertex>)vertices;

            for (int i = 0; i < primitives.Count; i++)
            {
                GDIPrimitives p = (GDIPrimitives)primitives[i];

                switch (p.Type)
                {
                    //case PrimitiveType.PointList:
                    //    break;
                    //case PrimitiveType.LineList:
                    //    break;
                    //case PrimitiveType.TriangleList:
                    //    break;
                    case PrimitiveType.TriangleStrip:
                        DrawTriangleStrip(p, v, transformation);
                        break;
                    //case PrimitiveType.TriangleFan:
                    //    break;
                    case PrimitiveType.LineStrip:
                        DrawLineStrip(p, v, transformation);
                        break;
                    default:
                        throw new ApplicationException("GDI GeometryObject: Primitive with type " + p.Type
                            + " is not supported.");
                }
            }
        }

        private void DrawTriangleStrip(GDIPrimitives p, GDIVertexArray<Vertex> v, Matrix transformation)
        {
            if (typeof(Vertex) != typeof(PositionColor))
                throw new ApplicationException("GDI GeometryObject: unsupported vertex type.");

            List<PositionColor> points = (List<PositionColor>)((object)v.VertexBuffer);

            double a11 = transformation.Elements[0],
                    a12 = transformation.Elements[1],
                    a21 = transformation.Elements[2],
                    a22 = transformation.Elements[3],
                    hx = transformation.Elements[4],
                    hy = transformation.Elements[5];
           
            Point[] pf = new Point[4];
            Color[] c = new Color[4];
            float a, r, g, b;
            for (int i = 3; i < p.Indices.Length; i += 2)
            {
                a = r = g = b = 0;
                for (int j = 0; j <= 3; j++)
                {
                    PositionColor pc = points[p.Indices[i - j]];

                    PointF point = new PointF(pc.X, pc.Y);
                    double x = point.X * a11 + point.Y * a12 + hx;
                    point.Y = (float)(point.X * a21 + point.Y * a22 + hy);
                    point.X = (float)x;
                    pf[j] = new Point((int)Math.Round(point.X), (int)Math.Round(point.Y));

                    c[j] = Color.FromArgb(pc.color);
                    r += c[j].R; g += c[j].G; b += c[j].B; a += c[j].A;
                }

                Point temp = pf[0];
                pf[0] = pf[1]; pf[1] = temp;
                Color ctemp = c[0];
                c[0] = c[1]; c[1] = ctemp;

                PathGradientBrush brush = new PathGradientBrush(pf);
                brush.CenterColor = Color.FromArgb((int)(a * 0.25),
                    (int)(r * 0.25), (int)(g * 0.25), (int)(b * 0.25));
                brush.SurroundColors = c;
                graphics.FillPolygon(brush, pf);                
                brush.Dispose();

                //if(i < (p.Indices.Length / 2))
                //graphics.DrawPolygon(Pens.BlueViolet, pf);
            }
        }

        private void FillGouraud(IGraphics g, PointF[] p, Color[] c)
        {
            SortPoints(p, c);
            LinearGradientBrush brush = null;
            Pen pen = null;
            double alphaX, alphaCR, alphaCG, alphaCB, betaX, betaCR, betaCG, betaCB;

            betaX = (p[2].X - p[0].X) / (p[2].Y - p[0].Y);
            betaCR = (double)(c[2].R - c[0].R) / (p[2].Y - p[0].Y);
            betaCG = (double)(c[2].G - c[0].G) / (p[2].Y - p[0].Y);
            betaCB = (double)(c[2].B - c[0].B) / (p[2].Y - p[0].Y);

            if (p[1].Y - p[0].Y >= 1)
            {
                alphaX = (p[1].X - p[0].X) / (p[1].Y - p[0].Y);
                alphaCR = (double)(c[1].R - c[0].R) / (p[1].Y - p[0].Y);
                alphaCG = (double)(c[1].G - c[0].G) / (p[1].Y - p[0].Y);
                alphaCB = (double)(c[1].B - c[0].B) / (p[1].Y - p[0].Y);

                float y0 = p[0].Y;
                float x0 = p[0].X;
                int c0R = c[0].R, c0G = c[0].G, c0B = c[0].B;
                for (float y = y0; y < p[1].Y; y += 1)
                {
                    float x = (float)((y - y0) * alphaX + x0);
                    int cR = (int)((y - y0) * alphaCR) + c0R;
                    int cG = (int)((y - y0) * alphaCG) + c0G;
                    int cB = (int)((y - y0) * alphaCB) + c0B;

                    float x_ = (float)((y - y0) * betaX + x0);
                    int cR_ = (int)((y - y0) * betaCR) + c0R;
                    int cG_ = (int)((y - y0) * betaCG) + c0G;
                    int cB_ = (int)((y - y0) * betaCB) + c0B;

                    if (brush == null)
                    {
                        //brush = new LinearGradientBrush(new PointF(x, y), new PointF(x_, y), Color.FromArgb(cR, cG, cB), Color.FromArgb(cR_, cG_, cB_));                        
                        pen = new Pen(brush);
                    }
                    g.DrawLine(pen, x, y, x_, y);

                    //pen.Dispose();
                    //brush.Dispose();
                }
            }

            brush.Dispose();

            g.DrawLine(Pens.Red, p[0], p[1]);
            g.DrawLine(Pens.Blue, p[0], p[2]);
            g.DrawLine(Pens.YellowGreen, p[1], p[2]);
        }

        private void SortPoints(PointF[] points, Color[] colors)
        {
            for (int i = 1; i < 3; i++)
            {
                if (points[i - 1].Y > points[i].Y)
                {
                    PointF p = points[i - 1]; points[i - 1] = points[i]; points[i] = p;
                    Color c = colors[i - 1]; colors[i - 1] = colors[i]; colors[i] = c;

                    if (i > 1) i -= 2;
                }
            }
        }

        private void DrawLineStrip(GDIPrimitives p, GDIVertexArray<Vertex> v, Matrix transformation)
        {
            // TODO: Draw multi-colour lines with LinearGradientBrush

            if (typeof(Vertex) != typeof(PositionColor))
                throw new ApplicationException("GDI GeometryObject: unsupported vertex type.");

            List<PositionColor> points = (List<PositionColor>)((object)v.VertexBuffer);
            PositionColor p0 = points[p.Indices[0]];
            PointF pt1, pt0 = new PointF(p0.X, p0.Y);

            double a11 = transformation.Elements[0],
                a12 = transformation.Elements[1],
                a21 = transformation.Elements[2],
                a22 = transformation.Elements[3],
                hx = transformation.Elements[4],
                hy = transformation.Elements[5];

            double x = pt0.X * a11 + pt0.Y * a12 + hx;
            pt0.Y = (float)(pt0.X * a21 + pt0.Y * a22 + hy);
            pt0.X = (float)x;

            using (Pen pen = new Pen(Color.FromArgb(p0.color)))
            {
                PointF[] polyline = new PointF[p.NumberOfPrimitives + 1];
                polyline[0] = pt0;

                for (int i = 1; i <= p.NumberOfPrimitives; i++)
                {
                    p0 = points[p.Indices[i]];

                    pt1 = new PointF(p0.X, p0.Y);
                    x = pt1.X * a11 + pt1.Y * a12 + hx;
                    pt1.Y = (float)(pt1.X * a21 + pt1.Y * a22 + hy);
                    pt1.X = (float)x;

                    //graphics.DrawLine(pen, pt0, pt1);
                    pt0 = pt1;
                    polyline[i] = pt1;
                }

                graphics.DrawLines(pen, polyline);
            }
        }

        private void DrawLineStrip(GDIPrimitives p, GDIVertexArray<Vertex> v)
        {
            // TODO: Draw multi-colour lines with LinearGradientBrush

            if(typeof(Vertex) != typeof(PositionColor))
                throw new ApplicationException("GDI GeometryObject: unsupported vertex type.");

            List<PositionColor> points = (List<PositionColor>)((object)v.VertexBuffer);
            PositionColor p0 = points[p.Indices[0]];
            PointF pt1, pt0 = new PointF(p0.X, p0.Y);

            using (Pen pen = new Pen(Color.FromArgb(p0.color)))
            {
                for (int i = 1; i <= p.NumberOfPrimitives; i++)
                {
                    p0 = points[p.Indices[i]];
                    pt1 = new PointF(p0.X, p0.Y);
                    graphics.DrawLine(pen, pt0, pt1);
                    pt0 = pt1;
                }
            }
        }

        #region IDisposable Members

        public override void Dispose()
        {
            base.Dispose();
        }

        #endregion       
    }

    public class GDICompositeGeometricObject : CompositeGeometricObject, IGDIGeometricObject
    {
        private IGraphics graphics;

        public GDICompositeGeometricObject(IGraphics graphics)
        {
            this.graphics = graphics;  
        }

        public override PrimitiveGeometricObject<Vertex> CreatePrimitiveGeometricObject<Vertex>()
        {
            return new GDIPrimitiveGeometricObject<Vertex>(graphics);
        }

        /// <summary>
        /// Draws all descendant geometric objects in the current context.
        /// </summary>
        public void Draw()
        {
            for (int i = 0; i < objects.Count; i++)
            {
                if (objects[i] is IGDIGeometricObject)
                    (objects[i] as IGDIGeometricObject).Draw();
            }

            DrawText();
        }

        /// <summary>
        /// Draws all descendant geometric objects in the current context.
        /// </summary>
        public void Draw(Matrix transformation)
        {
            for (int i = 0; i < objects.Count; i++)
            {
                if (objects[i] is IGDIGeometricObject)
                    (objects[i] as IGDIGeometricObject).Draw(transformation);
            }

            DrawText(transformation);
        }

        private void DrawText()
        {
            using (Font font = new Font("Verdana", 8))
            {
                SolidBrush brush = null;

                for (int i = 0; i < labels.Count; i++)
                {
                    TextLabel label = labels[i];

                    if (brush == null || brush.Color != label.Color)
                    {
                        brush.Dispose();
                        brush = new SolidBrush(label.Color);
                    }

                    graphics.DrawString(label.Text, font, brush, label.X, label.Y);
                }
            }
        }

        private void DrawText(Matrix transform)
        {
            double
                a11 = transform.Elements[0],
                a12 = transform.Elements[1],
                a21 = transform.Elements[2],
                a22 = transform.Elements[3],
                hx = transform.Elements[4],
                hy = transform.Elements[5];

                        
            using (Font font = //new Font("Verdana", 8.0f))
                new Font("Verdana", (float)Math.Min(8.0f, (4.0f * Math.Min(Math.Abs(a11), Math.Abs(a22))))))
            using(Brush textBack = new SolidBrush(Color.White))
            {
                SolidBrush brush = new SolidBrush(Color.Black);
                float x, y;
                Matrix m = graphics.Transform;
                
                for (int i = 0; i < labels.Count; i++)
                {
                    TextLabel label = labels[i];

                    //if (brush == null || brush.Color != label.Color)
                    //{
                    //    if(brush != null)
                    //        brush.Dispose();
                    //    brush = new SolidBrush(label.Color);
                    //}

                    SizeF size = graphics.MeasureString(label.Text, font);

                    x = (float)(a11 * label.X + a12 * label.Y + hx);
                    y = (float)(a21 * label.X + a22 * label.Y + hy);
                    
                    graphics.TranslateTransform(x, y);
                    graphics.RotateTransform(GetAngle(label.Dx, label.Dy));
                    graphics.TranslateTransform(-x, -y);

                    x -= size.Width * 0.5f;
                    y -= size.Height * 0.5f;
                    graphics.FillRectangle(textBack, x, y, size.Width, size.Height);
                    graphics.DrawString(label.Text, font, brush, x, y);

                    graphics.Transform = m;                    
                }

                brush.Dispose();
            }
        }

        private float GetAngle(float dx, float dy)
        {
            if (dx == 0)
                return 0;

            return (float)((-Math.Atan(dy / dx)) * 180.0 / Math.PI);
        }

        public IGraphics IGraphics
        {
            get
            {
                return graphics;
            }
        }

        public override void Dispose()
        {
            base.Dispose();
        }
    }
}
